﻿using System;
using System.Linq;
using System.Data;
using System.Text;
using System.Drawing;
using System.IO.Ports;
using System.Windows.Forms;
using System.ComponentModel;
using System.Collections.Generic;

using HypermarketSerialPort.Properties;
using System.Threading;
using System.IO;

namespace HypermarketSerialPort
{
    #region Public Enumerations
    public enum DataMode { Text, Hex }
    public enum LogMsgType { Incoming, Outgoing, Normal, Warning, Error };
    #endregion

    

    public partial class FormSerialPort : Form
    {
        #region Local Variables
        // Various colors for logging info
        private Color[] LogMsgTypeColor = { Color.Blue, Color.Green, Color.Black, Color.Orange, Color.Red };
        // Temp holder for whether a key was pressed
        private bool KeyHandled = false;
        private Settings settings = Settings.Default;
        private _2_Controller.CoordinatingController mCController = _2_Controller.CoordinatingController.getInstance();
        #endregion

        #region Constructor
        public FormSerialPort()
        {
            // Load user settings
            settings.Reload();

            // Build the form
            InitializeComponent();

            // Restore the users settings
            InitializeControlValues();

            // Enable/disable controls based on the current state
            EnableControls();
        }

        /// <summary> Populate the form's controls with default settings. </summary>
        private void InitializeControlValues()
        {
            cmbParity.Items.Clear(); 
            cmbParity.Items.AddRange(Enum.GetNames(typeof(Parity)));
            cmbStopBits.Items.Clear(); 
            cmbStopBits.Items.AddRange(Enum.GetNames(typeof(StopBits)));
            cmbParity.Text = settings.Parity.ToString();
            cmbBaudRate.Text = settings.BaudRate.ToString();
            cmbParity.Text = settings.Parity.ToString();
            cmbStopBits.Text = settings.StopBits.ToString();
            cmbDataBits.Text = settings.DataBits.ToString();
            CurrentDataMode = settings.DataMode;

            RefreshComPortList();

            // If it is still avalible, select the last com port used
            if (cmbPort.Items.Contains(settings.PortName)) 
                cmbPort.Text = settings.PortName;
            else if (cmbPort.Items.Count > 0)
                cmbPort.SelectedIndex = Constant.CMBPORT_INIT;
            else
            {
                MessageBox.Show(this, Constant.IINT_COMPORT_ERROR_MSG, Constant.INIT_COMPORT_ERROR_CAPTION, MessageBoxButtons.OK, MessageBoxIcon.Error);
                this.Close();
            }
        }

        /// <summary> Enable/disable controls based on the app's current state. </summary>
        private void EnableControls()
        {
            // Enable/disable controls based on whether the port is open or not
            gbPortSettings.Enabled = !serialPortHypermarket.IsOpen;
            txtSendData.Enabled = btnSend.Enabled = serialPortHypermarket.IsOpen;
            //chkDTR.Enabled = chkRTS.Enabled = comport.IsOpen;

            if (serialPortHypermarket.IsOpen) 
                btnOpenPort.Text = "&Close Port";
            else 
                btnOpenPort.Text = "&Open Port";
        }
        #endregion

        #region Local Methods

        private void RefreshComPortList()
        {
            // Determain if the list of com port names has changed since last checked
            string selected = RefreshComPortList(cmbPort.Items.Cast<string>(), cmbPort.SelectedItem as string, serialPortHypermarket.IsOpen);

            // If there was an update, then update the control showing the user the list of port names
            if (!String.IsNullOrEmpty(selected))
            {
                cmbPort.Items.Clear();
                cmbPort.Items.AddRange(OrderedPortNames());
                cmbPort.SelectedItem = selected;
            }
        }

        private string RefreshComPortList(IEnumerable<string> PreviousPortNames, string CurrentSelection, bool PortOpen)
        {
            // Create a new return report to populate
            string selected = null;

            // Retrieve the list of ports currently mounted by the operating system (sorted by name)
            string[] ports = SerialPort.GetPortNames();

            // First determain if there was a change (any additions or removals)
            bool updated = PreviousPortNames.Except(ports).Count() > 0 || ports.Except(PreviousPortNames).Count() > 0;

            // If there was a change, then select an appropriate default port
            if (updated)
            {
                // Use the correctly ordered set of port names
                ports = OrderedPortNames();

                // Find newest port if one or more were added
                string newest = SerialPort.GetPortNames().Except(PreviousPortNames).OrderBy(a => a).LastOrDefault();

                // If the port was already open... (see logic notes and reasoning in Notes.txt)
                if (PortOpen)
                {
                    if (ports.Contains(CurrentSelection)) 
                        selected = CurrentSelection;
                    else if (!String.IsNullOrEmpty(newest)) 
                        selected = newest;
                    else 
                        selected = ports.LastOrDefault();
                }
                else
                {
                    if (!String.IsNullOrEmpty(newest)) 
                        selected = newest;
                    else if (ports.Contains(CurrentSelection)) 
                        selected = CurrentSelection;
                    else selected = 
                        ports.LastOrDefault();
                }
            }

            // If there was a change to the port list, return the recommended default selection
            return selected;
        }

        /// <summary> Save the user's settings. </summary>
        private void SaveSettings()
        {
            settings.BaudRate = int.Parse(cmbBaudRate.Text);
			settings.DataBits = int.Parse(cmbDataBits.Text);
			settings.DataMode = CurrentDataMode;
			settings.Parity = (Parity)Enum.Parse(typeof(Parity), cmbParity.Text);
			settings.StopBits = (StopBits)Enum.Parse(typeof(StopBits), cmbStopBits.Text);
			settings.PortName = cmbPort.Text;

			settings.Save();
        }

        /// <summary> Send the user's data currently entered in the 'send' box.</summary>
        private void SendData()
        {
            if (CurrentDataMode == DataMode.Text)
            {
                // Send the user's text straight out the port
                serialPortHypermarket.Write(txtSendData.Text);
    
                // Show in the terminal window the user's text
                Log(LogMsgType.Outgoing, txtSendData.Text + "\n");
            }
            else
            {
                 try
                 {
                     // Convert the user's string of hex digits (ex: B4 CA E2) to a byte array
                     byte[] data = HexStringToByteArray(txtSendData.Text);

                     // Send the binary data out the port
                     serialPortHypermarket.Write(data, 0, data.Length);

                     // Show the hex digits on in the terminal window
                     Log(LogMsgType.Outgoing, ByteArrayToHexString(data) + "\n");
                 }
                 catch (FormatException)
                 {
                     // Inform the user if the hex string was not properly formatted
                     Log(LogMsgType.Error, "Not properly formatted hex string: " + txtSendData.Text + "\n");
                 }
            }
            txtSendData.SelectAll();
        }

        private void ClearTerminal()
        {
            txtTerminal.Clear();
        }

        private string[] OrderedPortNames()
        {
            // Just a placeholder for a successful parsing of a string to an integer
            int num;

            // Order the serial port names in numberic order (if possible)
            return SerialPort.GetPortNames().OrderBy(a => a.Length > 3 && int.TryParse(a.Substring(3), out num) ? num : 0).ToArray();
        }

        /// <summary> Log data to the terminal window. </summary>
        /// <param name="msgType"> The type of message to be written. </param>
        /// <param name="msg"> The string containing the message to be shown. </param>
        private void Log(LogMsgType msgType, string msg)
        {
            txtTerminal.Invoke(new EventHandler(delegate
                {
                    txtTerminal.SelectedText = string.Empty;
                    txtTerminal.SelectionFont = new Font(txtTerminal.SelectionFont, FontStyle.Bold);
                    txtTerminal.SelectionColor = LogMsgTypeColor[(int)msgType];
                    txtTerminal.AppendText(msg);
                    txtTerminal.ScrollToCaret();
                }));
        }

        /// <summary> Convert a string of hex digits (ex: E4 CA B2) to a byte array. </summary>
        /// <param name="s"> The string containing the hex digits (with or without spaces). </param>
        /// <returns> Returns an array of bytes. </returns>
        private byte[] HexStringToByteArray(string s)
        {
            s = s.Replace(" ", "");
            byte[] buffer = new byte[s.Length / 2];
            for (int i = 0; i < s.Length; i += 2)
            {
                buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i, 2), 16);
            }
            return buffer;
        }

        /// <summary> Converts an array of bytes into a formatted string of hex digits (ex: E4 CA B2)</summary>
        /// <param name="data"> The array of bytes to be translated into a string of hex digits. </param>
        /// <returns> Returns a well formatted string of hex digits with spacing. </returns>
        private string ByteArrayToHexString(byte[] data)
        {
            StringBuilder sb = new StringBuilder(data.Length * 3);
            foreach (byte b in data)
            {
                sb.Append(Convert.ToString(b, 16).PadLeft(2, '0').PadRight(3, ' '));
            }
            return sb.ToString().ToUpper();
        }

        #endregion

        #region Local Properties

        private DataMode CurrentDataMode
        {
            get
            {
                if (rbHex.Checked) return DataMode.Hex;
                else return DataMode.Text;
            }
            set
            {
                if (value == DataMode.Text) rbText.Checked = true;
                else rbHex.Checked = true;
            }
        }

        #endregion

        #region Event Handlers
    
        private void FormSerialPort_Shown(object sender, EventArgs e)
        {
            Log(LogMsgType.Normal, String.Format("Application Started at {0}\n", DateTime.Now));
        }

        private void FormSerialPort_FormClosing(object sender, FormClosingEventArgs e)
        {
            // The form is closing, save the user's preferences
            SaveSettings();
            serialPortHypermarket.Close();
        }

        private void rbText_CheckedChanged(object sender, EventArgs e)
        { 
            if (rbText.Checked) 
                CurrentDataMode = DataMode.Text; 
        }

        private void rbHex_CheckedChanged(object sender, EventArgs e)
        { 
            if (rbHex.Checked) 
                CurrentDataMode = DataMode.Hex; 
        }

        private void cmbBaudRate_Validating(object sender, CancelEventArgs e)
        { 
            int x; 
            e.Cancel = !int.TryParse(cmbBaudRate.Text, out x); 
        }

        private void cmbDataBits_Validating(object sender, CancelEventArgs e)
        {
            int x; 
            e.Cancel = !int.TryParse(cmbDataBits.Text, out x); 
        }

        private void btnOpenPort_Click(object sender, EventArgs e)
        {
            bool error = false;
            
            // If the port is open, close it.
            if (serialPortHypermarket.IsOpen) 
                serialPortHypermarket.Close();
            else
            {
                // Set the port's settings
                serialPortHypermarket.BaudRate = int.Parse(cmbBaudRate.Text);
                serialPortHypermarket.DataBits = int.Parse(cmbDataBits.Text);
                serialPortHypermarket.StopBits = (StopBits)Enum.Parse(typeof(StopBits), cmbStopBits.Text);
                serialPortHypermarket.Parity = (Parity)Enum.Parse(typeof(Parity), cmbParity.Text);
                serialPortHypermarket.PortName = cmbPort.Text;

                try
                {
                    // Open the port
                    serialPortHypermarket.Open();
                }
                catch (UnauthorizedAccessException) 
                { 
                    error = true; 
                }
                catch (IOException) 
                { 
                    error = true; 
                }
                catch (ArgumentException) 
                { 
                    error = true; 
                }

                if (error) 
                    MessageBox.Show(this, Constant.OPEN_COMPORT_ERROR_MSG, Constant.OPEN_COMPORT_ERROR_CAPTION, MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }

            // Change the state of the form's controls
             EnableControls();

            // If the port is open, send focus to the send data box
            if (serialPortHypermarket.IsOpen)
            {
                txtSendData.Focus();
            }
        }

        private void btnSend_Click(object sender, EventArgs e)
        { 
            SendData(); 
        }

        private void btnReceive_Click(object sender, EventArgs e)
        {
            ReceiveData();
        }

        private void ReceiveData()
        {
            string input = txtReceiveData.Text.ToString();
            //if (!input.Equals(string.Empty))
            //{
            //    Log(LogMsgType.Incoming, input + "\n");
            //    byte[] output = mCController.StartOperation(input);
            //    if(serialPortHypermarket.IsOpen)
            //        serialPortHypermarket.Write(output, 0, output.Length);
            //    string o = ByteArrayToHexString(output);
            //    Log(LogMsgType.Outgoing, o + "\n");
            //}

            if (!input.Equals(string.Empty))
            {
                Log(LogMsgType.Incoming, input + "\n");
                byte[] data = HexStringToByteArray(input);
                byte[] output = mCController.StartOperation(data);
                if(serialPortHypermarket.IsOpen)
                    serialPortHypermarket.Write(output, 0, output.Length);
                string o = ByteArrayToHexString(output);
                Log(LogMsgType.Outgoing, o + "\n");
            }
        }

        private void txtReceiveData_KeyDown(object sender, KeyEventArgs e)
        {
            // If the user presses [ENTER], send the data now
            if (KeyHandled = e.KeyCode == Keys.Enter) { e.Handled = true; ReceiveData(); }
        }

        private void txtReceiveData_KeyPress(object sender, KeyPressEventArgs e)
        {
            e.Handled = KeyHandled;
        }

        private void serialPortHypermarket_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            // If the com port has been closed, do nothing
            if (!serialPortHypermarket.IsOpen) return;

            // This method will be called when there is data waiting in the port's buffer
            // Determain which mode (string or binary) the user is in
            if (CurrentDataMode == DataMode.Text)
            {
                // Read all the data waiting in the buffer
                string data = serialPortHypermarket.ReadExisting();

                // Display the text to the user in the terminal
                Log(LogMsgType.Incoming, data);
            }
            else
            {
                // Obtain the number of bytes waiting in the port's buffer
                int bytes = serialPortHypermarket.BytesToRead;

                // Create a byte array buffer to hold the incoming data
                byte[] buffer = new byte[bytes];

                // Read the data from the port and store it in our buffer
                serialPortHypermarket.Read(buffer, 0, bytes);

                // Show the user the incoming data in hex format
                Log(LogMsgType.Incoming, ByteArrayToHexString(buffer));
                byte[] output = mCController.StartOperation(buffer);
                
                if (serialPortHypermarket.IsOpen && output != null)
                {
                    Log(LogMsgType.Outgoing, ByteArrayToHexString(output));
                    serialPortHypermarket.Write(output, 0, output.Length);
                }
            }

            /*
             * To Do send to database to get information
             */
        }

        private void txtSendData_KeyDown(object sender, KeyEventArgs e)
        { 
            // If the user presses [ENTER], send the data now
            if (KeyHandled = e.KeyCode == Keys.Enter) { e.Handled = true; SendData(); } 
        }

        private void txtSendData_KeyPress(object sender, KeyPressEventArgs e)
        { 
            e.Handled = KeyHandled; 
        }

		private void btnClear_Click(object sender, EventArgs e)
		{
			ClearTerminal();
		}

        private void tmrCheckComPorts_Tick(object sender, EventArgs e)
        {
            // checks to see if COM ports have been added or removed
            // since it is quite common now with USB-to-Serial adapters
            RefreshComPortList();
        }

        #endregion
	}
}